﻿using System;
using System.Collections.Generic;
using System.Text;

namespace Ex04.Menus.Delegates
{
    public delegate void SelectionDelegate();

    public class MenuItem
    {
        public event SelectionDelegate m_Selected;

        private const int k_FirstOptionIndex = 1;
        private const int k_LowestOptionIndex = 0;
        private const int k_BackOrExitIndex = 0;
        private const int k_EmptyList = 0;
        private const bool k_BaseMenu = true;

        private List<MenuItem> m_MenuItems = null;
        private string m_Title;
        private MenuItem m_ParentMenu = null;
        private int? m_OptionChosen = null;
        private bool m_isBaseMenuItem = !k_BaseMenu;
        
        /// <summary>
        /// A constructor 
        /// </summary>
        /// <param name="i_Title">
        ///     The title of the new menu item
        ///     <remarks>
        ///         Must not be null, in such case will throw ArgumentNullException
        ///     </remarks>
        /// </param>
        public MenuItem(string i_Title)
        {
            if (i_Title == null)
            {
                throw new ArgumentNullException("null title is invalid");
            }

            m_Title = i_Title;
        }

        /// <summary>
        /// A property to get and set the Selected delegate
        /// </summary>
        public SelectionDelegate Selected
        {
            get { return m_Selected; }
            set { m_Selected += value; }
        }

        internal static int BackOrExitIndex
        {
            get { return k_BackOrExitIndex; }
        }

        internal List<MenuItem> MenuItems
        {
            get { return m_MenuItems; }
        }

        internal bool IsBaseMenuItem
        {
            get { return m_isBaseMenuItem; }
            set { m_isBaseMenuItem = value; }
        }

        internal int OptionChosen
        {
            get { return m_OptionChosen.Value; }
        }

        /// <summary>
        /// A property to get the menu item title
        /// </summary>
        public string Title
        {
            get { return m_Title; }
        }

        internal string ParentMenuTitle
        {
            get { return m_ParentMenu != null ? m_ParentMenu.Title : m_Title; }
        }

        /// <summary>
        /// Will add a sub-menu to this menu item 
        /// </summary>
        /// <param name="i_MenuItemToAdd">
        ///     The sub-menu to add
        ///     <remarks>
        ///         Must not be null, in such case will throw ArgumentNullException
        ///     </remarks>
        /// </param>
        public void AddMenuItem(MenuItem i_MenuItemToAdd)
        {
            if (i_MenuItemToAdd == null)
            {
                throw new ArgumentNullException("null menu item can not be added");
            }
            
            if (m_MenuItems == null)
            {
                m_MenuItems = new List<MenuItem>();
            }
            
            m_MenuItems.Add(i_MenuItemToAdd);
            MenuItem newMenuItemAdded = i_MenuItemToAdd as MenuItem;
            if (newMenuItemAdded != null)
            {
                newMenuItemAdded.m_ParentMenu = this;
            }
        }

        /// <summary>
        /// This is the menu item main execution function.
        /// If this menu item is an action menu item then it should not have any
        /// sub-menus (m_MenuItems should be null). If this menu item is a node
        /// (i.e. having sub-menus) it should not have an action (a function added to its delegate).
        /// Upon selection this function the value of m_MenuItems and act accordingly.
        /// <remarks>
        ///     If this menu item is an action menu item and there is no function set
        ///     on its delegate this function will throw NoActionAddedException.
        ///     If this menu item is a node (i.e. having sub-menus) and its delegate 
        ///     has a function set to it, this function will be disregarded.
        /// </remarks>
        /// </summary>
        public void Execute()
        {
            Console.Clear();
            if (m_MenuItems == null || m_MenuItems.Count == k_EmptyList)
            {
                if (Selected == null)
                {
                    throw new NoActionAddedException("No action was set to this menu item");
                }

                Selected.Invoke();
            }
            else
            {
                Draw();
                AskUserToChooseAndGetValidatedChoice(out m_OptionChosen);
            }
        }

        internal bool IsSubMenu()
        {
            return m_MenuItems != null && m_MenuItems.Count != k_EmptyList;
        }

        private void AskUserToChooseAndGetValidatedChoice(out int? i_OptionChosen)
        {
            bool parsedOK, needToRetry;
            int optionChosen;
            string exitOrBackString;

            do
            {
                exitOrBackString = IsBaseMenuItem ? "exit" : "go back";
                Console.Write("Enter your choice (or {0} to {1}): ", k_BackOrExitIndex, exitOrBackString);
                parsedOK = int.TryParse(Console.ReadLine(), out optionChosen);
                needToRetry = !parsedOK || !isValidChoice(optionChosen);
                if (needToRetry)
                {
                    Console.WriteLine("Invalid choice, Enter a number between {0} and {1}!", k_LowestOptionIndex, m_MenuItems.Count);
                }

            } while (needToRetry);

            i_OptionChosen = optionChosen;
        }

        private bool isValidChoice(int optionChosen)
        {
            return (k_LowestOptionIndex <= optionChosen) && (optionChosen <= this.m_MenuItems.Count);
        }

        private void Draw()
        {
            StringBuilder message = new StringBuilder();
            string backExitSaying;

            message.AppendLine(ParentMenuTitle);
            message.AppendLine();
            for (int i = k_FirstOptionIndex; i <= MenuItems.Count; ++i)
            {
                message.AppendFormat("{0}. {1}", i, MenuItems[i - 1].Title);
                message.AppendLine();
            }
        
            backExitSaying = IsBaseMenuItem ? "Exit" : "Back";
            message.AppendLine(string.Format("{0}. {1}", k_BackOrExitIndex, backExitSaying));
            Console.WriteLine(message);
        }
    }
}
